package com.maegul.dao.implementations;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.MessageDigest;
import java.sql.SQLException;
import java.util.Arrays;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import com.maegul.dao.AuthUserDAO;
import com.maegul.dao.entities.AuthUser;

@Repository
public class HashAuthentication implements AuthUserDAO {

	private final static int ITERATION_NUMBER = 1000;

	@PersistenceContext(unitName = "maegul")
	protected EntityManager em;

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.maegul.dao.UserDAO#authenticate(java.lang.String,
	 * java.lang.String)
	 */
	@Override
	@Transactional
	public boolean authenticate(String login, String password)
			throws SQLException, NoSuchAlgorithmException {

		try {
			boolean userExist = true;

			if (login == null || password == null) {
				userExist = false;
				login = "";
				password = "";
			}

			Query q = em
					.createQuery("select au from AuthUser au where au.login =?1");
			q.setParameter(1, login);

			String digest, salt = "";
			AuthUser user = (AuthUser) q.getSingleResult();

			digest = user.getPassword();
			salt = user.getSalt();

			if (digest == null || salt == null) {
				throw new SQLException(
						"Database inconsistant Salt or Digested Password altered");
			} else {
				digest = "000000000000000000000000000=";
				salt = "00000000000=";
				userExist = false;
			}

			byte[] bDigest = base64ToByte(digest);
			byte[] bSalt = base64ToByte(salt);

			byte[] proposedDigest = getHash(ITERATION_NUMBER, password, bSalt);

			return Arrays.equals(proposedDigest, bDigest) && userExist;
		} catch (IOException e) {
			throw new SQLException(
					"Database inconsistant Salt or Digested Password altered");
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.maegul.dao.UserDAO#createUser(java.lang.String,
	 * java.lang.String)
	 */
	@Override
	@Transactional
	public boolean createUser(String login, String password)
			throws SQLException, NoSuchAlgorithmException {
		try {
			if (login != null && password != null && login.length() <= 100) {
				SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");

				byte[] bSalt = new byte[8];
				rand.nextBytes(bSalt);

				byte[] bDigest = getHash(ITERATION_NUMBER, password, bSalt);

				String sDigest = byteToBase64(bDigest);
				String sSalt = byteToBase64(bSalt);

				AuthUser newUser = new AuthUser();
				newUser.setUsername(login);
				newUser.setPassword(sDigest);
				newUser.setSalt(sSalt);
				em.persist(newUser);
				em.flush();

				return true;
			} else
				return false;
		} catch (IOException e) {
			return false;
		}
	}

	/**
	 * From a password, a number of iterations and a salt, returns the
	 * corresponding digest
	 * 
	 * @param iterations
	 *            int The number of iterations of the algorithm
	 * @param password
	 *            String The password to encrypt
	 * @param salt
	 *            byte[] The salt
	 * @return byte[] The digested password
	 * @throws NoSuchAlgorithmException
	 *             If the algorithm doesn't exist
	 */
	private byte[] getHash(int iterations, String password, byte[] salt)
			throws NoSuchAlgorithmException, UnsupportedEncodingException {
		MessageDigest digest = MessageDigest.getInstance("SHA1");
		digest.reset();
		digest.update(salt);
		byte[] input = digest.digest(password.getBytes("UTF-8"));

		for (int i = 0; i < iterations; i++) {
			digest.reset();
			input = digest.digest(input);
		}

		return input;
	}

	private static byte[] base64ToByte(String data) throws IOException {
		BASE64Decoder decoder = new BASE64Decoder();
		return decoder.decodeBuffer(data);
	}

	private static String byteToBase64(byte[] data) {
		BASE64Encoder encoder = new BASE64Encoder();
		return encoder.encode(data);
	}

}
